using System;
using Microsoft.Xna.Framework;
using TTXNA.src.Util;
using System.Collections;

namespace TTXNA.src.Engine.Graphics
{
    public class TTCamera : TTUpdateable
    {

        private Matrix _viewMatrix;
        private Matrix _projectionMatrix;

        private Vector3 _right;
        private Vector3 _up;
        public Vector3 _look;
        private Vector3 _position; //private Vector3 _cameraPosition;
        private Vector3 _lookAt; //private Vector3 _cameraTarget;
        
        private Vector3 _velocity;
        private Vector3 _angularVelocity;

        private float width;
        private float height;

        private float _yaw; //private float _heading;
        private float _pitch;
        private float _roll;

        private float _fieldOfView = (float)Math.PI / 4.0f;
        private float _nearPlane = 0.1f;
        private float _farPlane = 100000.0f;

        private Vector3[] _frustumCorners;
        private Plane[] _frustumPlanes;

        private float _maxPitch = 0.96f;
        private float _maxVelocity = 5.0f;
        //private float _maxAngular = MathHelper.PiOver2;

        GameTime second = new GameTime(new TimeSpan(), new TimeSpan(), new TimeSpan(), new TimeSpan(0, 0, 1));

        public TTCamera(float a, float b)
        {
            this.width = a;
            this.height = b;

            _viewMatrix = Matrix.Identity;
            _projectionMatrix = Matrix.CreatePerspectiveFieldOfView(_fieldOfView, a / b, _nearPlane, _farPlane);

            _position = new Vector3(15, 1.0f, 15);
            _velocity = Vector3.Zero;
            _angularVelocity = Vector3.Zero;
            _look = new Vector3(-1, 0, -1.0f);
            _up = Vector3.Up;

            _frustumCorners = new Vector3[8];
            _frustumPlanes = new Plane[6];

            //Update(gt);
        }

        public void ChangeAspectRatio(float width, float height)
        {
            _projectionMatrix = Matrix.CreatePerspectiveFieldOfView(_fieldOfView, width / height, _nearPlane, _farPlane);
        }

        public void Update(GameTime elapsedTime)
        {
            //if (Math.Abs(_angularVelocity.X) > _maxAngular)
            //    _angularVelocity.X = _maxAngular * Math.Sign(_angularVelocity.X);
            //if (Math.Abs(_angularVelocity.Y) > _maxAngular)
            //    _angularVelocity.Y = _maxAngular * Math.Sign(_angularVelocity.Y);

            Vector3 angularChange = _angularVelocity * (float)elapsedTime.ElapsedGameTime.TotalSeconds;

            _look = Vector3.Transform(_look, Matrix.CreateRotationY(angularChange.Y));

            Vector3 oldLook = _look;
            _look = Vector3.Transform(_look, Matrix.CreateFromAxisAngle(_right, angularChange.X));

            if (Math.Abs(_look.Y) > _maxPitch)
                _look = oldLook;

            _look.Normalize();

            _right = Vector3.Cross(_look, Vector3.Up);

            _angularVelocity = Vector3.Zero;

            float trueVelocity = _velocity.Length();

            if (trueVelocity > _maxVelocity)
            {
                _velocity /= (trueVelocity / _maxVelocity);
            }

            _position += _velocity * (float)elapsedTime.ElapsedGameTime.TotalSeconds;

            //if (_position.Y < 0.25f)
            //    _position.Y = 0.25f;

            _lookAt = _position + _look;

            _velocity = Vector3.Zero;

            _viewMatrix = Matrix.CreateLookAt(_position, _lookAt, Vector3.Up);

            ComputeViewFrustum();
        }

        public void UpdateNow()
        {
            Update(second);
        }

        public void MoveForward(float units)
        {
            // Move forward and back
            Vector3 change = _look * units;
            change.Y = 0;

            _velocity += change;
            //_velocity.Y = 0;
            //Update();
        }

        public void Strafe(float units)
        {
            // Move left and right
            _velocity += _right * units;
            //Update();
        }

        public void Zoom(float units)
        {
            _velocity += _look * units;
            //Update();
        }

        public void UpDown(float units)
        {
            _velocity.Y += units;
        }

        public void AdjustYaw(float radians)
        {
            _angularVelocity.Y -= radians;
        }

        public void AdjustPitch(float radians)
        {
            _angularVelocity.X -= radians;
        }

        public void RotateAroundLook(float radians)
        {
            float diff = _position.Y / -_look.Y;
            Vector3 groundPoint = Vector3.Add(_position, Vector3.Multiply(_look, diff));

            Matrix rotation = Matrix.CreateRotationY(radians);

            Vector3 diff2 = Vector3.Subtract(_position, groundPoint);

            Vector3 diff3 = Vector3.TransformNormal(diff2, rotation);

            _position = Vector3.Add(groundPoint, diff3);
            _look = Vector3.TransformNormal(_look, rotation);

            //Update();
        }

        public void AdjustRoll(float radians)
        {
            if (radians == 0.0f)
                return;

            Matrix rotation = Matrix.CreateFromAxisAngle(_look, radians);
            _right = Vector3.TransformNormal(_right, rotation);
            _up = Vector3.TransformNormal(_up, rotation);
        }
        
        private void ComputeViewFrustum()
        {
            Matrix matrix = _viewMatrix * _projectionMatrix;
            Matrix.Invert(ref matrix, out matrix);

            _frustumCorners[0] = new Vector3(-1.0f, -1.0f, 0.0f); // xyz
            _frustumCorners[1] = new Vector3(1.0f, -1.0f, 0.0f); // Xyz
            _frustumCorners[2] = new Vector3(-1.0f, 1.0f, 0.0f); // xYz
            _frustumCorners[3] = new Vector3(1.0f, 1.0f, 0.0f); // XYz
            _frustumCorners[4] = new Vector3(-1.0f, -1.0f, 1.0f); // xyZ
            _frustumCorners[5] = new Vector3(1.0f, -1.0f, 1.0f); // XyZ
            _frustumCorners[6] = new Vector3(-1.0f, 1.0f, 1.0f); // xYZ
            _frustumCorners[7] = new Vector3(1.0f, 1.0f, 1.0f); // XYZ

            for (int i = 0; i < _frustumCorners.Length; i++)
                _frustumCorners[i] = Vector3.Transform(_frustumCorners[i], matrix);

            // Now calculate the planes
            _frustumPlanes[0] = new Plane(_frustumCorners[0], _frustumCorners[1], _frustumCorners[2]); // Near
            _frustumPlanes[1] = new Plane(_frustumCorners[6], _frustumCorners[7], _frustumCorners[5]); // Far
            _frustumPlanes[2] = new Plane(_frustumCorners[2], _frustumCorners[6], _frustumCorners[4]); // Left
            _frustumPlanes[3] = new Plane(_frustumCorners[7], _frustumCorners[3], _frustumCorners[5]); // Right
            _frustumPlanes[4] = new Plane(_frustumCorners[2], _frustumCorners[3], _frustumCorners[6]); // Top
            _frustumPlanes[5] = new Plane(_frustumCorners[1], _frustumCorners[0], _frustumCorners[4]); // Bottom
        }

        public bool IsInViewFrustum(TTObject unitToCheck)
        {
            foreach (Plane plane in _frustumPlanes)
            {
                if (plane.Normal.X * unitToCheck.GetPosition().X + plane.Normal.Y * unitToCheck.GetPosition().Y + plane.Normal.Z * unitToCheck.GetPosition().Z + plane.D <= (-unitToCheck.radius))
                    return false;
            }

            return true;
        }

        public bool IsInViewFrustum(float x, float y, float z, float radius)
        {
            foreach (Plane plane in _frustumPlanes)
            {
                if (plane.Normal.X * x + plane.Normal.Y * y + plane.Normal.Z * z + plane.D <= (-radius))
                    return false;
            }

            return true;
        }

        public bool IsInViewFrustum2(float x, float y, float z, float radius)
        {
            float distance = 0.0f;

            foreach (Plane plane in _frustumPlanes)
            {
                distance = plane.DotCoordinate(new Vector3(x, y, z));

                if (distance <= (-radius))
                    return false;
            }
            return true;
        }

        /*
        public bool IsInViewFrustum2(UnitBase unitToCheck)
        {
            float distance = 0.0f;

            foreach (Plane plane in _frustumPlanes)
            {
                distance = plane.Dot(unitToCheck.Position);

                if (distance <= (-unitToCheck.Radius))
                    return false;
            }
            return true;
        }
        */

        public float GetWidth()
        {
            return width;
        }

        public float GetHeight()
        {
            return height;
        }

        public float Heading
        {
            get
            {
                return MathHelper.ToDegrees(_yaw);
            }
        }

        public float Pitch
        {
            get { return MathHelper.ToDegrees(_pitch); }
        }

        public float Roll
        {
            get { return MathHelper.ToDegrees(_roll); }
        }

        public Matrix View
        {
            get { return _viewMatrix; }
        }

        public Matrix Projection
        {
            get { return _projectionMatrix; }
        }

        public Vector3 Position
        {
            get { return _position; }
            set { _position = value; }
        }

        public Vector3 LookAt
        {
            get { return _lookAt; }
            set { _lookAt = value; }
        }

        public Vector3 Look
        {
            get { return _look; }
        }

        public float MaxVelocity
        {
            get { return _maxVelocity; }
            set { _maxVelocity = value; }
        }

        public float FarClipPlane
        {
            get { return _farPlane; }
            set { _farPlane = value; }
        }

    }
}
